using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using Bookvar.ObjectModel;
using Bookvar.UI.Arrangers;
using Bookvar.UI.DragDrop;
using Bookvar.UI.ViewModel;

namespace Bookvar.UI
{
    public class MindMapPanel : Canvas
    {
        private readonly Binding topicTemplateBinding;
        private readonly Binding topicTemplateSelectorBinding;

        //private bool animating;
        private bool arrangeInvalidated = true;
        private bool topicTemplatesInitialized;
        private bool isFirstTimeInitialization = true;

        private TopicContentPresenter mainTopicPresenter;

        #region Events

        #region SelectedTopicChanged

        public static readonly RoutedEvent SelectedTopicChangedEvent = EventManager.RegisterRoutedEvent(
            "SelectedTopicChangedEvent", RoutingStrategy.Direct, typeof ( EventHandler<SelectedTopicChangedEventArgs> ),
            typeof ( MindMapPanel ) );

        public event EventHandler<SelectedTopicChangedEventArgs> SelectedTopicChanged
        {
            add { AddHandler( SelectedTopicChangedEvent, value ); }
            remove { RemoveHandler( SelectedTopicChangedEvent, value ); }
        }

        #endregion

        #endregion

        #region Properties

        #region MainTopic

        public static readonly DependencyProperty MainTopicProperty = DependencyProperty.Register(
            "MainTopic", typeof ( MainTopic ), typeof ( MindMapPanel ), MainTopicPropertyMetadata );

        public MainTopic MainTopic
        {
            get { return (MainTopic) GetValue( MainTopicProperty ); }
            set { SetValue( MainTopicProperty, value ); }
        }


        private static PropertyMetadata MainTopicPropertyMetadata
        {
            get
            {
                FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata();
                fpm.AffectsMeasure = true;
                fpm.AffectsArrange = true;
                fpm.AffectsRender = true;
                fpm.PropertyChangedCallback =
                    delegate( DependencyObject element, DependencyPropertyChangedEventArgs args )
                        {
                            MindMapPanel panel = (MindMapPanel) element;
                            panel.MainTopicPropertyChanged( args );
                        };
                return fpm;
            }
        }

        #endregion

        #region Mindmap

        public static readonly DependencyProperty MindmapProperty = DependencyProperty.Register(
            "Mindmap", typeof ( MindMap ), typeof ( MindMapPanel ), MindmapPropertyMetadata );

        public MindMap Mindmap
        {
            get { return (MindMap) GetValue( MindmapProperty ); }
            set { SetValue( MindmapProperty, value ); }
        }

        private static PropertyMetadata MindmapPropertyMetadata
        {
            get
            {
                FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata();
                fpm.AffectsMeasure = true;
                fpm.AffectsArrange = true;
                fpm.AffectsRender = true;
                fpm.PropertyChangedCallback =
                    delegate( DependencyObject element, DependencyPropertyChangedEventArgs args )
                        {
                            MindMapPanel panel = (MindMapPanel) element;
                            panel.MindMapPropertyChanged( args );
                        };
                return fpm;
            }
        }

        #endregion

        #region SelectedTopic

        public static readonly DependencyProperty SelectedTopicProperty =
            DependencyProperty.Register( "SelectedTopic", typeof ( Topic ), typeof ( MindMapPanel ),
                                         new FrameworkPropertyMetadata( OnSelectedTopicChanged ) );

        public Topic SelectedTopic
        {
            get { return (Topic) GetValue( SelectedTopicProperty ); }
            set { SetValue( SelectedTopicProperty, value ); }
        }

        #endregion

        #region TopicArranger

        public TopicArranger TopicArranger
        {
            get { return (TopicArranger) GetValue( TopicArrangerProperty ); }
            set { SetValue( TopicArrangerProperty, value ); }
        }

        private void OnChangeArangerAnimationCompleted()
        {
            TopicConnector.ConnectAll( mainTopicPresenter );
            InvalidateTopicsArrange();
            topicTemplatesInitialized = false;
            //animating = false;
        }

        public static readonly DependencyProperty TopicArrangerProperty = DependencyProperty.Register(
            "TopicArranger", typeof ( TopicArranger ), typeof ( MindMapPanel ), TopicArrangerPropertyMetadata );

        private static PropertyMetadata TopicArrangerPropertyMetadata
        {
            get
            {
                FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata();
                fpm.AffectsArrange = true;
                fpm.PropertyChangedCallback += OnTopicArrangerPropertyChanged;
                return fpm;
            }
        }

        internal static void OnTopicArrangerPropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e )
        {
            MindMapPanel panel = (MindMapPanel) d;
            //panel.animating = true;

            if ( panel.mainTopicPresenter != null )
            {
                panel.TopicConnector.DisconnectAll( panel.mainTopicPresenter );
                SetIsMovedRecursively( panel.mainTopicPresenter, false );
                PanelLayoutAnimator.Animate( panel, panel.OnChangeArangerAnimationCompleted );

                panel.InvalidateTopicsArrange();
                panel.topicTemplatesInitialized = false;
            }
        }

        #endregion

        #region TopicConnector

        public ITopicConnector TopicConnector
        {
            get { return (ITopicConnector) GetValue( TopicConnectorProperty ); }
            set { SetValue( TopicConnectorProperty, value ); }
        }

        public static readonly DependencyProperty TopicConnectorProperty = DependencyProperty.Register(
            "TopicConnector", typeof ( ITopicConnector ), typeof ( MindMapPanel ), TopicConnectorPropertyMetadata );


        private static PropertyMetadata TopicConnectorPropertyMetadata
        {
            get
            {
                FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
                //metadata.DefaultValue = new BezierTopicConnector();
                metadata.PropertyChangedCallback =
                    delegate( DependencyObject element, DependencyPropertyChangedEventArgs args )
                        {
                            MindMapPanel panel = (MindMapPanel) element;
                            if ( panel.mainTopicPresenter != null )
                            {
                                ( (ITopicConnector) args.OldValue ).DisconnectAll( panel.mainTopicPresenter );
                                ( (ITopicConnector) args.NewValue ).ConnectAll( panel.mainTopicPresenter );
                            }
                        };
                return metadata;
            }
        }

        #endregion

        #region TopicTemplate

        public static readonly DependencyProperty TopicTemplateProperty = DependencyProperty.Register(
            "TopicTemplate", typeof ( DataTemplate ), typeof ( MindMapPanel ), new FrameworkPropertyMetadata( null ) );

        public DataTemplate TopicTemplate
        {
            get { return (DataTemplate) GetValue( TopicTemplateProperty ); }
            set { SetValue( TopicTemplateProperty, value ); }
        }

        #endregion

        #region TopicTemplateSelector

        public static readonly DependencyProperty TopicTemplateSelectorProperty =
            DependencyProperty.Register( "TopicTemplateSelector", typeof ( DataTemplateSelector ),
                                         typeof ( MindMapPanel ),
                                         new FrameworkPropertyMetadata( null ) );

        public DataTemplateSelector TopicTemplateSelector
        {
            get { return (DataTemplateSelector) GetValue( TopicTemplateSelectorProperty ); }
            set { SetValue( TopicTemplateSelectorProperty, value ); }
        }

        #endregion

        #region Skin

        public static readonly DependencyProperty SkinProperty =
            DependencyProperty.Register( "Skin", typeof ( Uri ), typeof ( MindMapPanel ), SkinPropertyMetadata );

        public Uri Skin
        {
            get { return (Uri) GetValue( SkinProperty ); }
            set { SetValue( SkinProperty, value ); }
        }


        private static PropertyMetadata SkinPropertyMetadata
        {
            get
            {
                FrameworkPropertyMetadata fpm = new FrameworkPropertyMetadata();
                fpm.DefaultValue =
                    new Uri( "pack://application:,,,/BookvarWPF;component/Resources/Topic/DefaultStyles.xaml",
                             UriKind.Absolute );
                fpm.AffectsRender = true;
                fpm.AffectsArrange = true;
                fpm.PropertyChangedCallback =
                    delegate( DependencyObject element, DependencyPropertyChangedEventArgs args )
                        {
                            MindMapPanel panel = (MindMapPanel) element;
                            panel.Resources.Source = (Uri) args.NewValue;
                            panel.InvalidateTopicsArrange();
                            panel.topicTemplatesInitialized = false;
                            object backgroundBrush = panel.TryFindResource( "MindMapBackgroud" );
                            panel.Background = (Brush) backgroundBrush;
                        };
                return fpm;
            }
        }

        #endregion

        public Rect SurroundingRectangle
        {
            get { return SizeHelper.GetMapRectangle( this ); }
        }

        public IEnumerable<TopicContentPresenter> ChildPresenters
        {
            get
            {
                foreach ( UIElement child in InternalChildren )
                {
                    if ( child is TopicContentPresenter )
                    {
                        yield return (TopicContentPresenter) child;
                    }
                }
            }
        }

        public TopicContentPresenter MainTopicPresenter
        {
            get { return mainTopicPresenter; }
        }

        internal Binding TopicTemplateBinding
        {
            get { return topicTemplateBinding; }
        }

        internal Binding TopicTemplateSelectorBinding
        {
            get { return topicTemplateSelectorBinding; }
        }

        #endregion

        [SuppressMessage( "Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline" )]
        static MindMapPanel()
        {
            AllowDropProperty.OverrideMetadata(
                typeof ( MindMapPanel ), new FrameworkPropertyMetadata( BooleanBoxes.TrueBox ) );
            BackgroundProperty.OverrideMetadata(
                typeof ( MindMapPanel ), new FrameworkPropertyMetadata( Brushes.Transparent ) );
        }

        public MindMapPanel()
        {
            var viewModel = new MindmapViewModel();
            ViewModelBase.AddCommandAndInputBindings( this, viewModel );

            SetBinding( MindmapProperty,
                        new Binding( "Mindmap" ) { Source = viewModel, Mode = BindingMode.TwoWay } );
            SetBinding( TopicArrangerProperty,
                        new Binding( "TopicArranger" ) { Source = viewModel, Mode = BindingMode.TwoWay } );
            SetBinding( TopicConnectorProperty,
                        new Binding( "TopicConnector" ) { Source = viewModel, Mode = BindingMode.TwoWay } );
            SetBinding( SelectedTopicProperty,
                        new Binding( "SelectedTopic" ) { Source = viewModel, Mode = BindingMode.TwoWay } );

            topicTemplateBinding = new Binding( TopicTemplateProperty.Name ) { Source = this };
            topicTemplateSelectorBinding = new Binding( TopicTemplateSelectorProperty.Name ) { Source = this };

            //BUG: if default value of dependency property is used Presentation Mode throws error
            TopicConnector = new BezierTopicConnector();

            TopicArranger = new MindmapTopicArranger( this );

            //apply default skin
            var resources = new ResourceDictionary { Source = Skin };
            Resources = resources;

            DragDropManager.SetDragSourceAdvisor( this, new MindmapPanelDragSourceAdvisor() );
            DragDropManager.SetDropTargetAdvisor( this, new MindmapPanelDropTargetAdvisor() );
        }

        protected virtual void MainTopicPropertyChanged( DependencyPropertyChangedEventArgs args )
        {
            if ( mainTopicPresenter != null )
            {
                TopicConnector.DisconnectAll( mainTopicPresenter );
                mainTopicPresenter.Delete();
            }

            InitializeMainTopic();

            //InvalidateTopicsArrange();
            topicTemplatesInitialized = false;

            //ResetTopicsBinding();
        }

        private void MindMapPropertyChanged( DependencyPropertyChangedEventArgs args )
        {
            var mindMap = args.OldValue as MindMap;
            if ( mindMap != null )
            {
                foreach ( Topic topic in mindMap.FloatingTopics )
                {
                    TopicContentPresenter presenter = FindTopicContentPresenter( topic.ID );
                    if ( presenter != null )
                    {
                        TopicConnector.DisconnectAll( presenter );
                        presenter.Delete();
                    }
                }

                mindMap.FloatingTopics.CollectionChanged -= OnFloatingTopicsChanged;
            }

            mindMap = args.NewValue as MindMap;
            if ( mindMap != null )
            {
                MainTopic = mindMap.MainTopic;
                topicTemplatesInitialized = false;
                //InvalidateTopicsArrange();

                InitializeFloatingTopics();
                mindMap.FloatingTopics.CollectionChanged += OnFloatingTopicsChanged;
            }
        }

        private void OnFloatingTopicsChanged( object sender, NotifyCollectionChangedEventArgs e )
        {
            switch ( e.Action )
            {
                case NotifyCollectionChangedAction.Add :
                {
                    foreach ( Topic topic in e.NewItems )
                    {
                        var child = new TopicContentPresenter( topic, this );
                        AddChild( child );
                        SelectedTopic = child.Topic;
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Remove :
                {
                    foreach ( Topic topic in e.OldItems )
                    {
                        TopicContentPresenter deleted = FindTopicContentPresenter( topic.ID );
                        if ( deleted != null )
                        {
                            TopicConnector.DisconnectAll( deleted );
                            deleted.Delete();
                            if ( deleted.ParentTopicPresenter != null )
                            {
                                SelectedTopic = deleted.ParentTopicPresenter.Topic;
                            }
                        }
                    }
                    break;
                }
                case NotifyCollectionChangedAction.Move :
                case NotifyCollectionChangedAction.Replace :
                case NotifyCollectionChangedAction.Reset :
                default :
                {
                    break;
                }
            }

            InvalidateTopicsArrange();
        }

        private void InitializeMainTopic()
        {
            mainTopicPresenter = new TopicContentPresenter( MainTopic, this );
            AddChild( mainTopicPresenter );

            SelectedTopic = mainTopicPresenter.Topic;
        }

        private void InitializeFloatingTopics()
        {
            foreach ( Topic topic in Mindmap.FloatingTopics )
            {
                var presenter = new TopicContentPresenter( topic, this );
                AddChild( presenter );
            }
        }

        internal void AddChild( TopicContentPresenter child )
        {
            Children.Add( child );
            if ( child.HasParent )
            {
                TopicConnector.Connect( child.ParentTopicPresenter, child );
            }

            SetTopicPositionBindings( child );

            child.LoadAdorners();
        }

        internal void RemoveChild( TopicContentPresenter child )
        {
            Children.Remove( child );
            if ( child.HasParent )
            {
                TopicConnector.Disconnect( child.ParentTopicPresenter, child );
            }
        }

        private void SetTopicPositionBindings( TopicContentPresenter presenter )
        {
            TopicPosition position = GetTopicPosition(presenter);
            presenter.SetBinding(LeftProperty, new Binding("X") { Source = position, Mode = BindingMode.TwoWay });
            presenter.SetBinding(TopProperty, new Binding("Y") { Source = position, Mode = BindingMode.TwoWay });
            presenter.SetBinding( TopicContentPresenter.IsMovedProperty,
                                  new Binding( "IsMoved" ) { Source = position, Mode = BindingMode.TwoWay } );
        }

        private TopicPosition GetTopicPosition(TopicContentPresenter presenter)
        {
            TopicPosition position;
            if (!Mindmap.TopicPositions.TryGetItem(presenter.Topic.ID, out position))
            {
                position = new TopicPosition(presenter.Topic.ID, presenter.X, presenter.Y, presenter.IsMoved);
                Mindmap.TopicPositions.Add(position);
            }
            return position;
        }

        public TopicContentPresenter GetTopicPresenterByPosition( Point position )
        {
            foreach ( TopicContentPresenter presenter in ChildPresenters )
            {
                Rect rect = new Rect( presenter.X, presenter.Y, presenter.ActualWidth, presenter.ActualHeight );
                if ( rect.Contains( position ) )
                {
                    return presenter;
                }
            }
            return null;
        }

        public IList<TopicContentPresenter> GetTopicPresentersByPosition( Point position )
        {
            var presenters = new List<TopicContentPresenter>();
            foreach ( TopicContentPresenter presenter in ChildPresenters )
            {
                var rect = new Rect( presenter.X, presenter.Y, presenter.ActualWidth, presenter.ActualHeight );
                if ( rect.Contains( position ) )
                {
                    presenters.Add( presenter );
                }
            }
            return presenters;
        }

        protected virtual void OnSelectedTopicChanged( RoutedEventArgs e )
        {
        }

        private static void SetIsMovedRecursively( TopicContentPresenter presenter, bool isMoved )
        {
            presenter.IsMoved = isMoved;
            if ( presenter.Children.Count > 0 )
            {
                foreach ( TopicContentPresenter child in presenter.Children )
                {
                    SetIsMovedRecursively( child, isMoved );
                }
            }
        }

        public void InvalidateTopicsArrange()
        {
            arrangeInvalidated = true;
        }

        public void InvalidateTopics()
        {
            InvalidateTopicsArrange();
            topicTemplatesInitialized = false;
        }

        protected override Size ArrangeOverride( Size finalSize )
        {
            if ( arrangeInvalidated )
            {
                EnsureTopicTemplates();
                //rearrange to apply actual sizes
                base.ArrangeOverride( finalSize );

                if ( mainTopicPresenter != null )
                {
                    TopicArranger.ArrangeMindmap( mainTopicPresenter, finalSize );
                    if ( isFirstTimeInitialization )
                    {
                        topicTemplatesInitialized = false;
                        EnsureTopicTemplates();
                        isFirstTimeInitialization = false;
                    }

                    arrangeInvalidated = false;
                }

                EnsureMapSize();

                //apply layout changes due to new arrangment
                UpdateLayout();
            }
            else
            {
                // Backword compatibility fix: In old file version the position of the MainTopic is not persisted
                // so it's position is default to (0,0). If so arrange only the main topic.
                if ( mainTopicPresenter != null && mainTopicPresenter.X == 0 && mainTopicPresenter.Y == 0 )
                {
                    TopicArranger.ArrangeMainTopic( mainTopicPresenter, finalSize );
                }
            }
            return base.ArrangeOverride( finalSize );
        }

        private void EnsureMapSize()
        {
            Rect surroundingRectangle = SurroundingRectangle;
            if ( ( Width / 2 ) < surroundingRectangle.Width )
            {
                Width = 2 * surroundingRectangle.Width;
            }
            if ( ( Height / 2 ) < surroundingRectangle.Height )
            {
                Height = 2 * surroundingRectangle.Height;
            }
        }

        private void EnsureTopicTemplates()
        {
            if ( !topicTemplatesInitialized )
            {
                if ( TopicTemplateSelector != null )
                {
                    foreach ( TopicContentPresenter presenter in ChildPresenters )
                    {
                        presenter.ContentTemplate = TopicTemplateSelector.SelectTemplate( this, presenter );
                    }
                }

                topicTemplatesInitialized = true;

                //                if(!animating)
                //                {
                //                    TopicConnector.DisconnectAll(mainTopicPresenter);
                //                    TopicConnector.ConnectAll(mainTopicPresenter);
                //                }
            }
        }

        private static void OnSelectedTopicChanged( DependencyObject element, DependencyPropertyChangedEventArgs e )
        {
            MindMapPanel panel = (MindMapPanel) element;
            SelectedTopicChangedEventArgs args =
                new SelectedTopicChangedEventArgs( SelectedTopicChangedEvent, e.OldValue as Topic, e.NewValue as Topic );

            if ( panel.TopicTemplateSelector != null )
            {
                if ( args.OldSelectedTopic != null )
                {
                    var oldPresenter = panel.FindTopicContentPresenter( args.OldSelectedTopic.ID );
                    if ( oldPresenter != null )
                    {
                        oldPresenter.ContentTemplate =
                            panel.TopicTemplateSelector.SelectTemplate( args.OldSelectedTopic, oldPresenter );
                    }
                }
                if ( args.NewSelectedTopic != null )
                {
                    var newPresenter = panel.FindTopicContentPresenter( args.NewSelectedTopic.ID );
                    if ( newPresenter != null )
                    {
                        newPresenter.ContentTemplate =
                            panel.TopicTemplateSelector.SelectTemplate( args.NewSelectedTopic, newPresenter );
                        newPresenter.Focus();
                    }
                }
            }

            panel.OnSelectedTopicChanged( args );
        }

        [SuppressMessage( "Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID" )]
        public TopicContentPresenter FindTopicContentPresenter( Guid topicID )
        {
            foreach ( TopicContentPresenter presenter in ChildPresenters )
            {
                if ( presenter.Topic.ID == topicID )
                {
                    return presenter;
                }
            }
            return null;
        }

        protected override void OnMouseEnter( MouseEventArgs e )
        {
            base.OnMouseEnter( e );
            if ( !e.Handled )
            {
                if ( !IsFocused )
                {
                    Focus();
                }
            }
        }
    }
}
